release: MVP feature enhancements and documentation config update#27
release: MVP feature enhancements and documentation config update#27salmanabdurrahman merged 5 commits intomainfrom
Conversation
- add DOCS_URL to .env.example - read DOCS_URL from environment variables using config - pass documentationUrl to welcome view from config
- add docs url to config file - update readme with docs url explanation
- add community post like table to prisma schema - create community post like table migration - implement toggleLikeOnPost service function to handle like/unlike logic - update addLikeHandler to use toggleLikeOnPost service - return like status and count in the response - add user id validation to addLikeHandler
…idation, and clean up error paths - Protect daily content route with requireAuth middleware - Allow shorter 'userWhy' in updateUserSettingsSchema (min length 3) - Remove prefix from zod error path for cleaner error messages
…y features (#26) feat: MVP patches and improvements for auth, validation, and community features
There was a problem hiding this comment.
Pull Request Overview
Prepares the next MVP release by securing routes, improving validation UX, introducing like/unlike functionality for community posts, and externalizing the docs URL into configuration.
- Protects daily content route with authentication middleware
- Adds toggle like/unlike with persistent like records and like count updates
- Introduces DOCS_URL config and updates welcome page to use it
Reviewed Changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| src/middleware/validate.middleware.ts | Normalizes Zod error paths by stripping body/query/params prefixes for cleaner client errors |
| src/config/index.ts | Adds docsUrl sourced from DOCS_URL env var |
| src/api/welcome/welcome.controller.ts | Uses config.docsUrl for documentation link instead of hardcoded value |
| src/api/users/user.validation.ts | Lowers minimum length for userWhy to 3 with updated message |
| src/api/content/content.routes.ts | Secures /daily route with requireAuth middleware |
| src/api/community/community.service.ts | Replaces addLike with toggleLike using CommunityPostLike join table and likeCount inc/dec in transactions |
| src/api/community/community.controller.ts | Updates like endpoint to toggle, includes user auth check, and returns isLiked + count |
| prisma/schema.prisma | Adds CommunityPostLike model and relations |
| prisma/migrations/.../migration.sql | Creates CommunityPostLike table, indexes, and FKs |
| README.md | Documents DOCS_URL env var |
| .env.example | Adds DOCS_URL placeholder |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
| if (existingLike) { | ||
| const updatedPost = await prisma.$transaction([ | ||
| prisma.communityPostLike.delete({ | ||
| where: { | ||
| userId_postId: { | ||
| userId, | ||
| postId, | ||
| }, | ||
| }, | ||
| }), | ||
| prisma.communityPost.update({ | ||
| where: { | ||
| id: postId, | ||
| }, | ||
| data: { | ||
| likeCount: { | ||
| decrement: 1, | ||
| }, | ||
| }, | ||
| }), | ||
| ]); | ||
| return { | ||
| likedCount: updatedPost[1].likeCount, | ||
| isLiked: false, | ||
| }; | ||
| } else { | ||
| const updatedPost = await prisma.$transaction([ | ||
| prisma.communityPostLike.create({ | ||
| data: { | ||
| userId, | ||
| postId, | ||
| }, | ||
| }), | ||
| prisma.communityPost.update({ | ||
| where: { | ||
| id: postId, | ||
| }, | ||
| data: { | ||
| likeCount: { | ||
| increment: 1, | ||
| }, | ||
| }, | ||
| }), | ||
| ]); | ||
| return { | ||
| likedCount: updatedPost[1].likeCount, | ||
| isLiked: true, | ||
| }; | ||
| } |
There was a problem hiding this comment.
There is a race between the existence check and the transaction, which can cause unique-constraint (P2002) or not-found (P2025) errors under concurrent like/unlike requests and return 500 instead of idempotent responses. Wrap each branch in try/catch and handle Prisma errors (treat P2002 as already liked and P2025 as already unliked), or move to an upsert-style flow (e.g., try create and on P2002 return isLiked=true without changing count, and similarly for delete on P2025) so the endpoint is idempotent under concurrency.
| const post = await toggleLikeOnPost(userId, postId); | ||
| const message = post.isLiked ? 'Postingan berhasil disukai' : 'Suka pada postingan dibatalkan'; | ||
|
|
||
| return successResponse(res, 200, message, post); |
There was a problem hiding this comment.
This changes the response shape from { likeCount } to { likedCount, isLiked }, which is a breaking change for clients. To maintain backward compatibility, return likeCount with the original key (e.g., { likeCount: post.likedCount, isLiked }) and consider keeping likedCount as an alias only if necessary.
| return successResponse(res, 200, message, post); | |
| return successResponse(res, 200, message, { | |
| likeCount: post.likedCount, | |
| isLiked: post.isLiked, | |
| // Optionally include likedCount as an alias for forward compatibility | |
| // likedCount: post.likedCount, | |
| // ...include other post properties if needed | |
| }); |
| const config = { | ||
| port: process.env.PORT || 3000, | ||
| databaseUrl: process.env.DATABASE_URL || '', | ||
| docsUrl: process.env.DOCS_URL || '', |
There was a problem hiding this comment.
Defaulting docsUrl to an empty string can render a blank documentation link on the welcome page when DOCS_URL is not set, regressing from the previous working hardcoded URL. Provide a sensible default (e.g., the prior Postman URL) or fallback in the controller: docsUrl: process.env.DOCS_URL || 'https://documenter.getpostman.com/view/38960737/2sB3QJQBZ8'.
| docsUrl: process.env.DOCS_URL || '', | |
| docsUrl: process.env.DOCS_URL || 'https://documenter.getpostman.com/view/38960737/2sB3QJQBZ8', |
| ALTER TABLE "public"."CommunityPostLike" ADD CONSTRAINT "CommunityPostLike_userId_fkey" FOREIGN KEY ("userId") REFERENCES "public"."User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; | ||
|
|
||
| -- AddForeignKey | ||
| ALTER TABLE "public"."CommunityPostLike" ADD CONSTRAINT "CommunityPostLike_postId_fkey" FOREIGN KEY ("postId") REFERENCES "public"."CommunityPost"("id") ON DELETE RESTRICT ON UPDATE CASCADE; |
There was a problem hiding this comment.
[nitpick] For a pure join table, ON DELETE CASCADE is typically preferred to avoid manual cleanup when deleting users or posts; RESTRICT will block deletions and can leave orphaned likeCount if deletes are forced elsewhere. Consider using ON DELETE CASCADE on both FKs.
| ALTER TABLE "public"."CommunityPostLike" ADD CONSTRAINT "CommunityPostLike_userId_fkey" FOREIGN KEY ("userId") REFERENCES "public"."User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; | |
| -- AddForeignKey | |
| ALTER TABLE "public"."CommunityPostLike" ADD CONSTRAINT "CommunityPostLike_postId_fkey" FOREIGN KEY ("postId") REFERENCES "public"."CommunityPost"("id") ON DELETE RESTRICT ON UPDATE CASCADE; | |
| ALTER TABLE "public"."CommunityPostLike" ADD CONSTRAINT "CommunityPostLike_userId_fkey" FOREIGN KEY ("userId") REFERENCES "public"."User"("id") ON DELETE CASCADE ON UPDATE CASCADE; | |
| -- AddForeignKey | |
| ALTER TABLE "public"."CommunityPostLike" ADD CONSTRAINT "CommunityPostLike_postId_fkey" FOREIGN KEY ("postId") REFERENCES "public"."CommunityPost"("id") ON DELETE CASCADE ON UPDATE CASCADE; |
Summary
Merging the
devbranch intomainfor the next MVP release, which introduces multiple improvements and new functionalities.Highlights
DOCS_URLenvironment variable for documentation reference.Deployment Notes
Run database migrations before deploying:
Ensure the production environment has
DOCS_URLproperly set.